iT邦幫忙

2022 iThome 鐵人賽

DAY 10
0

Cross-Contract Calls

Synchronization Link Tree


Calling Types

在智能合約的呼叫中我們必須知道一件事情那就是交易的原始發起者「必須是」一個 EOA,那如果我們在一個合約 A 中呼叫另外一個合約 B,要怎麼去決定 msg.sender 呢?

EOA -> Contract A -> Contract B

在以上的情況中,Contract B 的角色判斷如下:

Call Type msg.sender Context update State Usage
Call A B Yes 用於更新狀態(Write)
Call Code A A Yes 已經不能用囉
Delegate Call A's msg.sender A Yes 取代 "call code"、連接 Library
Static Call A B NO 單純查看狀態(View)
  1. .call
    • 沒有 gas 限制
    • <address>.call(bytes memory) returns (bool, bytes memory)
    • send 一樣,如果執行失敗不會停止而是 return false
    • 是較為適合轉入合約的狀況使用,因為 call 可以調整 gas ,也可以註明 Function Signature 來與轉入合約的 Function 做對接
    • <address>.call(bytes memory) returns (bool, bytes memory)
    • <address>.call{value: amount}( bytes memory ) 功能類似 .transfer 會從合約轉入帳號 amount 價值的 eth
    • <address>.call{gas: amount}( bytes memory ) 可以調整供給的 gas 數量,並且 returns 一個 boolean 值
  2. .delegatecall:和 call 基本上一樣,只是使用 delegatecall 時不能使用 value 但可以附帶註明 gas
  3. .staticcall:和 call 基本上一樣,只是使用 staticcall 時不能改變到 contract 中的任何狀態(static)

Example

我們可以從以下這個 EOA -> Contract A -> Contract B 的例子知道 call 是如何使用。

假設我們的合約是這樣:

contract ContractA {
    ContractB immutable public B;
    uint public aVal;
    constructor (address B_addr) {
        B = ContractB(B_addr);
    }
    function callB(bool _fail) external {
       // 我們要在這裡動手腳
    }
}

contract ContractB {
    uint public bVal;
    function fx(bool _fail) external returns (uint) {
        require(!_fail, "Failed");
        bVal = 1;
        return 2;
    }
}

callB() 中如果是正常的呼叫另外一個合約 B 的函式 fx()

function callB(bool _fail) external {
    aVal = B.fx(_fail); // 我們要在這裡動手腳
}

則 EVM bytecode 作用如下:

...
PC: 0x117, opcode: JUMPDEST
PC: 0x118, opcode: POP
PC: 0x119, opcode: GAS
PC: 0x11a, opcode: CALL     ─> aVal = B.fx(_fail);
PC: 0x11b, opcode: ISZERO   ─> 如果 call 回傳 0 就會產生 error
PC: 0x11c, opcode: DUP1     
PC: 0x11d, opcode: ISZERO           ┐   result = "the call returned 0"
PC: 0x11e, opcode: PUSH2 0x012b     ├─> if(result == 0) jump to 0x012b 並且 fetch aVal
PC: 0x121, opcode: JUMPI            ┘
PC: 0x122, opcode: RETURNDATASIZE   ┐
PC: 0x123, opcode: PUSH1 0x00       │   Otherwise forward the revert message.
PC: 0x125, opcode: DUP1             │   
PC: 0x126, opcode: RETURNDATACOPY   ├─> The revert message from ContractB is on the return data.
PC: 0x127, opcode: RETURNDATASIZE   │   
PC: 0x128, opcode: PUSH1 0x0        │   複製 ContractB 回傳來的 data 到 memory.
PC: 0x12a, opcode: REVERT           ┘   Push 到 stack
...

那如果我們是使用 callcallB() 中:

function callB(bool _fail) external {
    bool success;
    bytes memory returnValueEncoded;
    bytes memory funcParams = abi.encodeWithSelector(conB.stuff1.selector, _fail);

    (success, returnValueEncoded) = address(conB).call(funcParams);

    if (success) {
        val = abi.decode(returnValueEncoded, (uint256));
    }
    else {
        // Assume a revert (and not a panic or user defined function)
        assembly {
            // Remove the function selector.
            returnValueEncoded := add(returnValueEncoded, 0x04)
        }
        string memory revertReason = abi.decode(returnValueEncoded, (string));
        revert(revertReason);
    }
}

以上的第二個寫法我們就可以把 call 換成 delegateCall 或其他魔法,當然也可以考慮在這其中塞入更多動作讓這筆交易不是只有單純 cross contract call!這對 contractB 中有限定 msg.sender 或其他條件時有非常大的幫助。

Calldata

calldata 這個詞在以太坊中有兩種意思,一個是交易的 tx data,也就是交易中的 data field;另外一個則是 Solidity 中的記憶體儲存方式(與 storage, memory 一夥),現在要講的是前者,而後者我們留待 EVM & Memory Pool 的部分講述。

當我們要發起一筆交易來與合約函式互動時,to 指的就是合約地址,而 data 也就是 calldata,會包裹著 Function Selector 以及需要交付的參數。

例如我們要與 transfer(address _to, uint256 _value) 這個函式互動,他的 Function Selector(Method ID) 為 SHA-3 (or Keccak-256) 後取 4 bytes,則為:a9059cbb。而我們要輸入的參數有兩個,如果分別為 0xB42faBF7BCAE8bc5E368716B568a6f8Fdf3F84ec0x2a34892d36d6c74,我們可以得到最後要使用的 calldata 為:0xa9059cbb000000000000000000000000B42faBF7BCAE8bc5E368716B568a6f8Fdf3F84ec000000000000000000000000000000000000000000000000002a34892d36d6c74。那因為每個參數的長度為 32 bytes(也就是 64 hexadecimal characters),所以大家才會看見那麼多個 0。

每次在發動或查看各種 call 的結果時,總是和 Transaction 脫不了關係,如果想要更深入了解的話記得要關注這次的系列文,最後幾天會有詳細的介紹!


Closing

延伸閱讀: ERC-165

關於 Cross Contract Call 我們可以延伸閱讀可以把 interface 轉為 abstract contract 的 ERC-165。

Reference


最後歡迎大家拍打餵食大學生0x2b83c71A59b926137D3E1f37EF20394d0495d72d


上一篇
Day 9 - Assembly
下一篇
Day 11 - Optimal Gas Comsumption
系列文
Smart Contract Development Breakdown30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言